package com.yangxk.kadmin.web.module.gen.service;

import com.yangxk.kadmin.common.exception.BizException;
import com.yangxk.kadmin.common.support.generator.IdGenerator;
import com.yangxk.kadmin.generator.GenResult;
import com.yangxk.kadmin.generator.config.RelateTableConfig;
import com.yangxk.kadmin.generator.genfile.GeneratorFile;
import com.yangxk.kadmin.generator.KGenerator;
import com.yangxk.kadmin.generator.config.PackageConfig;
import com.yangxk.kadmin.generator.config.TableConfig;
import com.yangxk.kadmin.generator.field.Field;
import com.yangxk.kadmin.generator.template.TemplateType;
import com.yangxk.kadmin.generator.url.RequestUrl;
import com.yangxk.kadmin.generator.url.Url;
import com.yangxk.kadmin.generator.url.UrlDesc;
import com.yangxk.kadmin.web.common.Const;
import com.yangxk.kadmin.web.common.enumeration.ResponseCode;
import com.yangxk.kadmin.web.module.gen.entity.GenInfo;
import com.yangxk.kadmin.web.module.gen.entity.GenReportInfo;
import com.yangxk.kadmin.web.module.gen.entity.RelateTable;
import com.yangxk.kadmin.web.module.gen.po.GenLog;
import com.yangxk.kadmin.web.module.gen.po.GenLogFile;
import com.yangxk.kadmin.web.module.gen.po.GenLogTable;
import com.yangxk.kadmin.web.module.gen.util.DbUtil;
import com.yangxk.kadmin.web.module.gen.util.GenUtil;
import com.yangxk.kadmin.web.module.gen.vo.ShowTable;
import com.yangxk.kadmin.web.module.system.mapper.AuthMapper;
import com.yangxk.kadmin.web.module.system.po.Auth;
import com.yangxk.kadmin.web.module.system.service.RoleService;
import com.yangxk.kadmin.web.module.system.service.impl.AuthServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 代码生成
 *
 * @author yangxk
 * @date 2018/10/12 14:32
 */
@Service
public class GenService {

    @Value("${spring.datasource.druid.driver-class-name}")
    private String driver;
    @Value("${spring.datasource.druid.url}")
    private String url;
    @Value("${spring.datasource.druid.username}")
    private String username;
    @Value("${spring.datasource.druid.password}")
    private String password;

    @Autowired
    private AuthMapper authMapper;
    @Autowired
    private GenLogService genLogService;
    @Autowired
    private RoleService roleService;


    /**
     * 从数据库中获取所有表
     * create by yangxk on 2018/10/12 14:32
     */
    public List<ShowTable> getTables() throws Exception {
        Connection connection = null;
        try {
            connection = DbUtil.getConnection(driver, url, username, password);
            return DbUtil.getTables(connection);
        } finally {
            DbUtil.close(connection);
        }
    }

    /**
     * 从表中获取所有字段信息
     * create by yangxk on 2018/10/12 14:33
     */
    public List<Field> getColumns(String tableName) throws Exception {
        Connection connection = null;
        try {
            connection = DbUtil.getConnection(driver, url, username, password);
            return DbUtil.getColumns(connection, tableName);
        } finally {
            DbUtil.close(connection);
        }
    }

    /**
     * 通用代码生成
     * create by yangxk on 2018/10/12 14:33
     */
    @Transactional
    public void generator(GenInfo genInfo) {
        TableConfig tableConfig = new TableConfig();
        tableConfig.setComment(genInfo.getComment());
        tableConfig.setModelName(genInfo.getModelName());
        tableConfig.setZhModelName(genInfo.getZhModelName());
        tableConfig.setTableName(genInfo.getTableName());
        tableConfig.setFields(genInfo.getColumns());
        tableConfig.setDataScope(genInfo.isDataScope());
        tableConfig.setQueryAll(genInfo.isQueryAll());

        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setBasePackage(genInfo.getBasePackage());

        if (genInfo.getTemplateType() == TemplateType.relate) {
            Map<String, List<Field>> res = extractColumns(genInfo.getColumns());
            tableConfig.setFields(res.get("parent"));

            RelateTable relateTable = genInfo.getRelateTable();
            relateTable.setColumns(res.get("relate"));

            RelateTableConfig rt = configRelateInfo(relateTable);
            tableConfig.setRelateTableConfig(rt);
        }

        KGenerator generator = new KGenerator(packageConfig, tableConfig, genInfo.getTemplateType());
        GenResult genResult = generator.generator();

        // 执行数据库插入操作
        insertToDb(genInfo.getTableName(), genInfo.getZhModelName(), genInfo.getParentId(), genInfo.getTemplateType(), genResult);
    }

    private Map<String, List<Field>> extractColumns(List<Field> fieldList) {
        Map<String, List<Field>> result = new HashMap<>();
        List<Field> parent = new ArrayList<>();
        List<Field> relate = new ArrayList<>();

        int size = fieldList.size();
        int index = size;
        for (int i = 0; i < size; i++) {
            Field field = fieldList.get(i);
            if ("关联表字段:".equals(field.getColumnName())) {
                index = i;
            } else if (i < index) {
                parent.add(field); // 当前表字段
            } else {
                field.setRelate(true);
                relate.add(field); // 父表字段
            }
        }

        result.put("parent", parent);
        result.put("relate", relate);

        return result;
    }

    private RelateTableConfig configRelateInfo(RelateTable relateTable) {
        if (relateTable == null)
            throw new BizException(ResponseCode.ILLEGAL_PARAM);
        RelateTableConfig rt = new RelateTableConfig();
        rt.setTableName(relateTable.getTableName());
        rt.setModelName(relateTable.getModelName());
        rt.setFieldName(relateTable.getFieldName());
        rt.setParentTableField(relateTable.getParentTableField());
        rt.setPropertyName(GenUtil.getCamelCaseString(relateTable.getFieldName()));
        rt.setParentTablePropertyName(GenUtil.getCamelCaseString(relateTable.getParentTableField()));
        rt.setColumns(relateTable.getColumns());

        return rt;
    }

    /**
     * 报表代码生成
     * create by yangxk on 2019/2/22 16:00
     */
    @Transactional
    public void generateReport(GenReportInfo reportInfo) {
        TableConfig tableConfig = new TableConfig();
        tableConfig.setModelName(reportInfo.getModelName());
        tableConfig.setZhModelName(reportInfo.getZhModelName());
        Map<String, Object> config = new HashMap<>();
        config.put("reportFile", reportInfo.getReportFile());

        KGenerator generator = new KGenerator(null, tableConfig, TemplateType.report, config);
        GenResult genResult = generator.generator();

        insertToDb("", reportInfo.getZhModelName(), reportInfo.getParentId(), TemplateType.report, genResult);
    }

    /**
     * 执行数据库插入操作
     * create by yangxk on 2018/10/12 15:30
     */
    private void insertToDb(String tableName, String zhModelName, Long parentId, TemplateType templateType, GenResult genResult) {
        // 插入权限
        List<Auth> authList = insertAuth(zhModelName, parentId, genResult.getUrl());

        // 授权
        List<Long> roleAuthIds = insertRoleAuth(authList);

        // 插入生成日志
        inserGenLog(zhModelName, tableName, templateType,
                genResult.getGeneratorFileList(), authList, roleAuthIds);
    }

    /**
     * 为指定默认角色授权
     * create by yangxk 2018/10/21 20:58
     */
    private List<Long> insertRoleAuth(List<Auth> authList) {
        long roleId = 1; // 默认角色ID为1
        List<Long> authIds = authList.stream().map(item -> item.getId()).collect(Collectors.toList());
        return roleService.addAuth(roleId, authIds);
    }

    private void inserGenLog(String zhModelName, String tableName, TemplateType templateType,
                             List<GeneratorFile> generatorFileList, List<Auth> authList, List<Long> roleAuthIds) {
        GenLog genLog = buildGenLog(zhModelName, tableName, templateType);
        List<GenLogFile> genLogFiles = buildGenLogFile(generatorFileList, genLog.getId());

        List<Long> idList = authList.stream().map(item -> item.getId()).collect(Collectors.toList());
        List<GenLogTable> logTableList = buildGenLogTale(idList, "sys_auth", genLog.getId());
        List<GenLogTable> roleAuthList = buildGenLogTale(roleAuthIds, "sys_role_auth", genLog.getId());
        logTableList.addAll(roleAuthList);

        genLogService.addLog(genLog, genLogFiles, logTableList);
    }

    private GenLog buildGenLog(String zhModelName, String tableName, TemplateType templateType) {
        GenLog genLog = new GenLog();
        genLog.setId(IdGenerator.getId());
        genLog.setModelName(zhModelName);
        genLog.setTableName(tableName);
        genLog.setTemplateType(templateType.getDesc());
        genLog.setGenTime(new Date());
        genLog.setState(Const.GEN_STATE.NORMAL);
        genLog.setCreateTime(new Date());
        genLog.setUpdateTime(new Date());

        return genLog;
    }

    private List<GenLogFile> buildGenLogFile(List<GeneratorFile> generatorFileList, Long logId) {
        List<GenLogFile> result = new ArrayList<>(generatorFileList.size());
        GenLogFile logFile = null;
        for (GeneratorFile generatorFile : generatorFileList) {
            logFile = new GenLogFile();
            logFile.setId(IdGenerator.getId());
            logFile.setLogId(logId);
            logFile.setFileName(generatorFile.getFilename());
            logFile.setPath(generatorFile.getPath());
            if (generatorFile.isEdit()) {
                logFile.setType(Const.GEN_TYPE.UPDATE);
                logFile.setContent(generatorFile.getContent());
            } else {
                logFile.setType(Const.GEN_TYPE.GEN);
            }

            result.add(logFile);
        }

        return result;
    }

    private List<GenLogTable> buildGenLogTale(List<Long> idList, String tableName, Long logId) {
        List<GenLogTable> result = new ArrayList<>(idList.size());
        GenLogTable logTable = null;
        for (Long id : idList) {
            logTable = new GenLogTable();
            logTable.setId(IdGenerator.getId());
            logTable.setLogId(logId);
            logTable.setRecordId(String.valueOf(id));
            logTable.setTableName(tableName);

            result.add(logTable);
        }

        return result;
    }

    /**
     * 插入生成后的权限
     * create by yangxk on 2018/10/12 14:33
     */
    private List<Auth> insertAuth(String zhModelName, Long parentId, Url url) {
        String route = url.getRoute();
        Auth menu = buildMenu(route, zhModelName, parentId);

        List<UrlDesc> urlDescList = url.getUrlDesc();
        List<Auth> auths = buildAuth(urlDescList, menu.getId());

        auths.add(menu);

        authMapper.insertBatch(auths);

        return auths;
    }

    /**
     * 构建生成权限对象
     * create by yangxk on 2018/10/12 14:33
     */
    private List<Auth> buildAuth(Collection<UrlDesc> values, Long parentId) {
        List<Auth> result = new ArrayList<>(values.size());
        Auth auth = null;
        for (UrlDesc value : values) {
            auth = new Auth();
            auth.setId(IdGenerator.getId());
            auth.setParentId(parentId);
            auth.setUrl(value.getUrl());
            auth.setAuthName(value.getName());
            auth.setAuthCode(value.getCode());
            auth.setType(Const.AUTH_TYPE.BTN);
            auth.setCreateTime(new Date());
            auth.setUpdateTime(new Date());

            result.add(auth);
        }
        return result;
    }

    /**
     * 构建生成菜单对象
     * create by yangxk on 2018/10/12 14:34
     */
    private Auth buildMenu(String route, String zhModelName, Long parentId) {
        Auth menu = new Auth();
        menu.setId(IdGenerator.getId());
        menu.setParentId(parentId == null ? AuthServiceImpl.ROOT_PARENT_ID : parentId);
        menu.setRouter(route);
        menu.setAuthName(zhModelName);
        menu.setType(Const.AUTH_TYPE.MENU);
        menu.setCreateTime(new Date());
        menu.setUpdateTime(new Date());
        return menu;
    }

}
